/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package poproject.PlanningTask;

import JaCoP.core.IntVar;
import JaCoP.core.Store;
import JaCoP.core.Var;
import JaCoP.search.DepthFirstSearch;
import JaCoP.search.IndomainMin;
import JaCoP.search.Search;
import JaCoP.search.SelectChoicePoint;
import JaCoP.search.SimpleSelect;
import JaCoP.constraints.Diff2;
import JaCoP.constraints.Values;
import JaCoP.constraints.XneqY;
import JaCoP.search.SmallestDomain;
import java.util.ArrayList;
import poproject.Common.JaCoPEngine;
import poproject.Common.DataConverter;

/**
 *
 * @author janka_000
 */
public class PlanningTaskEngine implements JaCoPEngine{
    
    private final int RoundCount;   // Ilość rund w trakcie turnieju są dwie kolejka podstawowa i rewanżowa
    private int RoundMatchCount;    // Ilość meczy przypadających na jedną rundę
    private ArrayList<Var> mVars;   // Zmienne dla których propagowane są ograniczenia
    private Store mStore;           // Magazyn zmiennych i ograniczeń
    private Search mSearch;         // Metoda poszukiwań
    private ArrayList<FootballTeam> mTeamsArray;    // Lista drużyn
    private ArrayList<Number> mTimeArray;           // Lista zmiennych określająych ramy czasowe problemu
    private Integer mGamesWeekCount;        // Ilość tygodni w których rozgrywane są mecze
    private Integer mGamesDayCount;         // Ilość dni w tygodniu w których rozgrywane są mecze
    private int mContestDurationInDays;     // Czas trwania turnieju w dniach


    /**
     * Konstruktor silnika.
     * Konstruktor ten pozwala na utworzenie głównej części aplikacji 
     * odpowiedzialnej za rozwiązanie zadania w JaCoPie.
     * @param fieldsArray   Lista boisk na których rozgrywane są zawody
     * @param teamsArray    Lista zespołów biorących udział w rozgrywkach
     */
    public PlanningTaskEngine(ArrayList<FootballTeam> teamsArray, ArrayList<Number> timeArray)
    {
        RoundCount = 2;
        mStore = new Store();
        mVars = new ArrayList<>();
        mTeamsArray = teamsArray;
        mTimeArray = timeArray;
        mGamesWeekCount = mTimeArray.get(0).intValue();
        mGamesDayCount = mTimeArray.get(1).intValue();
        mContestDurationInDays = mGamesWeekCount*7;
        
        System.out.println("Ilość tygodni  "+mGamesWeekCount);
        System.out.println("Ilość dni meczowych w tygodniu "+mGamesDayCount);
        System.out.println("Czas turnieju (dni) "+mContestDurationInDays);
        System.out.println();
    }
    
    /**
     * Model problemu.
     * Metoda implementowana z interfejsu JaCoPEngine metodę pozwalającą
     * na stworzenie modelu problemu kombinatorycznego.
     */
    @Override
    public void createModel()
    {
        /**
         * Lista meczy.
         * Wyznaczenie meczy jakie występują w rozgrywkach.
         */
        ArrayList<String> gamesArray = DataConverter.teamArrayToFootbalGamesArray(mTeamsArray);
        RoundMatchCount = gamesArray.size();
        /**
         * Stworzenie boisk dla każdego spotkania.
         * Stworzenie zmiennych reprezentujących boiska w sposób zrozumiały 
         * dla JaCoPa. Należy pamiętać, że każde spotkanie definiuje
         * zarówno boisko jak i jego czas rozpoczęcia.
         */
        ArrayList<IntVar> gamesFields = new ArrayList<>();
        
        for(int round = 1 ;round <= RoundCount; ++round)
        {
            int field1=1, field2=2;
            for(String game : gamesArray)
            {
                IntVar iv = null;
                if (round == 1)
                    iv = new IntVar(mStore, "boisko(" + game + ")");
                else if(round == 2)
                    iv = new IntVar(mStore, "boiskoRewanz(" + game + ")");
                
                iv.addDom(field1, field1);
                iv.addDom(field2, field2);
                gamesFields.add(iv);

                // Taki zapis pozwala na stowrzenie zmienncyh IntVar o odpowiednich 
                // domenach. Ponieważ kolejność rozgrywek jest określona można
                // tak podejść do problemu.
                ++field2;
                if(field2>mTeamsArray.size())
                {
                    field1++;
                    field2 = field1 + 1;
                }
            }  
        }
        
        /**
         * Zapewnienie, że mecze rewanżowe odbędą się na innych boiskach
         */
        for(int match=0; match<RoundMatchCount; ++match)
            mStore.impose(new XneqY(gamesFields.get(match), gamesFields.get(match+RoundMatchCount)));

        /**
         * Stworzenie czasow rozpoczęć dla każdego spotkania.
         * Zmienne reprezentujące czasy rozpoczęć poszczególnych meczy.
         */
        ArrayList<IntVar> gamesStartTimes = new ArrayList<>();
        for(int round = 1; round <= RoundCount; ++round)
        {
            for(String game : gamesArray)
            {      
                IntVar iv = null;
                if (round == 1)
                {
                    iv = new IntVar(mStore, "czas(" + game + ")");
                    for(int week = 0; week < mGamesWeekCount/2; ++week) // Czasy pierwszej połowy sezonu
                    {
                        int startDay = week * 7 + 1;                // Dzień rozpoczynający rozgrywki w danym tygodniu
                        int endDay = startDay + mGamesDayCount - 1; // Dzień kończący rozgrywki w danym tygodniu
                        iv.addDom(startDay, endDay);
                    }
                }
                else if(round == 2)
                {
                    iv = new IntVar(mStore, "czasRewanz(" + game + ")");
                    for(int week = mGamesWeekCount/2; week < mGamesWeekCount; ++week)   // Czasy drugiej połowy sezonu
                    {
                        int startDay = week * 7 + 1;                // Dzień rozpoczynający rozgrywki w danym tygodniu
                        int endDay = startDay + mGamesDayCount - 1; // Dzień kończący rozgrywki w danym tygodniu
                        iv.addDom(startDay, endDay);
                    }
                }
                gamesStartTimes.add(iv); 
            }
        }
        
        for(IntVar iv : gamesFields)
            mVars.add(iv);
         
        for(IntVar iv : gamesStartTimes)
            mVars.add(iv);
               
        /**
         * Lista dni meczy meczy dla każdej drużyny.
         * Stworzenie osobnej listy dla każdej drużyny z dniami w których rozgyrwają
         * mecze.
         */
        ArrayList<ArrayList<IntVar>> daysForTeams = new ArrayList<>();
        
        for(FootballTeam team : mTeamsArray)
        {
            ArrayList<IntVar> teamDays = new ArrayList<>();
                        
            for(IntVar iv : gamesStartTimes)             
                if(iv.id.contains(team.getName()))
                    teamDays.add(iv);
            
            daysForTeams.add(teamDays);        
        }
              
        /**
         * Ograniczenie na ilość różnych dni.
         * Nie robi dokładnie tego co się chce tzn. nie ogranicza maksymalnej
         * ilości meczy danej drużyny do zadanej wartości ale ogranicza liczbę 
         * różnych dni.
         */
        for(int i=0; i<daysForTeams.size(); i++) //size() = ilość drużyn
        {
            int count = daysForTeams.get(i).size()-mTeamsArray.get(i).getMaxMatchesPerDay();            
            mStore.impose(new Values(daysForTeams.get(i), new IntVar(mStore, "licznik", count, count)));
        }
        
        /**
         * Stworzenie listy dla ograniczenia diff2.
         * Tworzymy listę która będzie reprezentowała poszczególne spotkania.
         * Każde ze spotkań opisane jest za pomocą:
         * [czasu rozpoczęcia meczu, boiska na którym jest mecz,
         * czasu trwania meczu (3), ilości potrzebnych zasobów]
         */
        ArrayList<ArrayList<IntVar>> gamesSchedule = new ArrayList<>();
        IntVar matchDuration = new IntVar(mStore, "CzasTrwaniaMeczu", 1, 1);    // Założenie że w danym dniu na jednym boisku odbywa się jeden mecz
        IntVar resourcesNo = new IntVar(mStore, "IlośćZasobów", 1, 1);
        for(int i=0; i<gamesArray.size(); i++)
        {
            ArrayList<IntVar> singleGame = new ArrayList<>();
            
            singleGame.add(gamesStartTimes.get(i));
            singleGame.add(gamesFields.get(i));
            singleGame.add(matchDuration); 
            singleGame.add(resourcesNo);
            
            gamesSchedule.add(singleGame);
        }
        /**
         * Nałożenie ograniczenia diff2 w celu rozdzielenia spotkań na
         * poszczególne boiska. Wstępnyc harmonogram rozgrywek.
         */
        mStore.impose(new Diff2(gamesSchedule));
    }
    
    /**
     * Poszukiwanie rozwiązania.
     * Metoda implementowana z interfejsu JaCoPEngine metodę pozwalającą
     * na znalezienie rozwiązania problemu kombinatorycznego.
     * 
     * @return Wartość boolean informująca czy znaleziono rozwiązanie. 
     */
    @Override
    public boolean searchSolution()
    {
        SelectChoicePoint select = new SimpleSelect(mVars.toArray(new Var[1]), new SmallestDomain(), new IndomainMin());
        mSearch = new DepthFirstSearch();
        
        boolean result = mSearch.labeling(mStore, select);
        
        return result;
    }
    
    /**
     * Pobranie meotdy poszukiwań.
     * Implementacja metody interfejsu pozwalająca na pobranie metody poszukiwań
     * problemu planowania/harmonogramowania w celu przetworzenia 
     * wyników poszukiwań do reprezentacji w GUI.
     * 
     * @return Metoda poszukiwań
     */
    @Override
    public Search getSearchMethod()
    {
        return mSearch;        
    }
    
}
